package com.cardone.common.template.support;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.UUID;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.ExtensionMethod;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * FreeMarkerTemplate支持
 * 
 * @author yaoht
 */
@Getter
@Setter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@ExtensionMethod({ org.springframework.util.Assert.class, org.apache.commons.io.IOUtils.class })
public class TemplateFreeMarkerSupport implements TemplateSupport {
	/**
	 * freemarker.template.Configuratio
	 */
	private Configuration configuration;

	/**
	 * 文件名解析格式
	 */
	private String filePathFormate = "${filePath}.ftl";

	/**
	 * 文件名变量名称
	 */
	private String filePathVariableName = "${filePath}";

	private Boolean showModel;

	private Boolean showProcessTemplateIntoString;

	private Boolean showTemplateString;

	/**
	 * 模板二级缓存
	 */
	private TemplateSupportCache templateSupportCache;

	/**
	 * 获取：缓存字符串
	 * 
	 * @param filePath
	 *            文件路径
	 * @return 缓存字符串
	 */
	private String getCacheStr(final String filePath) {
		if (this.templateSupportCache == null) {
			return null;
		}

		return this.templateSupportCache.getTemplateString(filePath);
	}

	/**
	 * 获取：新文件路径
	 * 
	 * @param filePath
	 *            文件路径
	 * @param isFormat
	 *            是否格式化文件路径
	 * @return 新文件路径
	 */
	private String getNewFilePath(final String filePath, final boolean isFormat) {
		if (!isFormat) {
			return filePath;
		}

		return StringUtils.replace(this.filePathFormate, this.filePathVariableName, filePath);
	}

	private void logModel(final Object model) {
		if (BooleanUtils.isTrue(this.showModel) && TemplateFreeMarkerSupport.log.isInfoEnabled()) {
			TemplateFreeMarkerSupport.log.info(ObjectUtils.toString(model));
		}
	}

	private void logStr(final String str) {
		if (BooleanUtils.isTrue(this.showProcessTemplateIntoString)) {
			TemplateFreeMarkerSupport.log.info(str);
		}
	}

	private void logTemplate(final Template template) {
		if (BooleanUtils.isTrue(this.showTemplateString) && TemplateFreeMarkerSupport.log.isInfoEnabled()) {
			TemplateFreeMarkerSupport.log.info(template.toString());
		}
	}

	@Override
	public String process(@NonNull final String filePath, final boolean isFormat, final Object model) {
		String str = this.getCacheStr(filePath);

		if (StringUtils.isNotBlank(str)) {
			return str;
		}

		final String newFilePath = this.getNewFilePath(filePath, isFormat);

		try {
			this.logModel(model);

			final Template template = this.configuration.getTemplate(newFilePath);

			this.logTemplate(template);

			str = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);

			this.logStr(str);

			this.setCacheStr(filePath, str, template);

			return str;
		} catch (final IOException e) {
			throw new IllegalArgumentException(e);
		} catch (final TemplateException e) {
			throw new IllegalArgumentException(e);
		}
	}

	@Override
	public String process(@NonNull final String filePath, final Object model) {
		return this.process(filePath, true, model);
	}

	@Override
	public String processString(@NonNull final String templateString, @NonNull final Object model) {
		StringWriter writer = null;

		Reader reader = null;

		try {
			writer = new StringWriter();

			final String name = UUID.randomUUID().toString();

			reader = new StringReader(templateString);

			final Template template = new Template(name, reader, this.configuration);

			template.process(model, writer);

			return writer.toString();
		} catch (final Exception e) {
			throw new RuntimeException(e);
		} finally {
			reader.closeQuietly();

			writer.closeQuietly();
		}
	}

	private void setCacheStr(final String filePath, final String str, final Template template) {
		if ((this.templateSupportCache != null) && org.apache.commons.lang3.StringUtils.equals(template.toString(), str)) {
			this.templateSupportCache.setTemplateString(filePath, str);
		}
	}
}
